﻿// Copyright 2004-2011 Castle Project - http://www.castleproject.org/
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#region 导入名称空间

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using LightWork.DynamicProxy.Generators;

#endregion

namespace LightWork.DynamicProxy.Internal
{
    public static class InvocationHelper
    {
        private static readonly Dictionary<KeyValuePair<MethodInfo, Type>, MethodInfo> cache =
            new Dictionary<KeyValuePair<MethodInfo, Type>, MethodInfo>();

        private static readonly Lock @lock = Lock.Create();

        public static MethodInfo GetMethodOnObject(object target, MethodInfo proxiedMethod)
        {
            if (target == null)
                return null;

            return GetMethodOnType(target.GetType(), proxiedMethod);
        }

        public static MethodInfo GetMethodOnType(Type type, MethodInfo proxiedMethod)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            Debug.Assert(proxiedMethod.DeclaringType.IsAssignableFrom(type),
                "proxiedMethod.DeclaringType.IsAssignableFrom(type)");
            using (var locker = @lock.ForReadingUpgradeable())
            {
                var methodOnTarget = GetFromCache(proxiedMethod, type);
                if (methodOnTarget != null)
                    return methodOnTarget;
                locker.Upgrade();

                methodOnTarget = GetFromCache(proxiedMethod, type);
                if (methodOnTarget != null)
                    return methodOnTarget;
                methodOnTarget = ObtainMethod(proxiedMethod, type);
                PutToCache(proxiedMethod, type, methodOnTarget);
                return methodOnTarget;
            }
        }

        private static MethodInfo GetFromCache(MethodInfo methodInfo, Type type)
        {
            var key = new KeyValuePair<MethodInfo, Type>(methodInfo, type);
            MethodInfo method;
            cache.TryGetValue(key, out method);
            return method;
        }

        private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type)
        {
            Type[] genericArguments = null;
            if (proxiedMethod.IsGenericMethod)
            {
                genericArguments = proxiedMethod.GetGenericArguments();
                proxiedMethod = proxiedMethod.GetGenericMethodDefinition();
            }
            var declaringType = proxiedMethod.DeclaringType;
            MethodInfo methodOnTarget = null;
            if (declaringType.IsInterface)
            {
                var mapping = type.GetInterfaceMap(declaringType);
                var index = Array.IndexOf(mapping.InterfaceMethods, proxiedMethod);
                Debug.Assert(index != -1);
                methodOnTarget = mapping.TargetMethods[index];
            }
            else
            {
                // NOTE: this implementation sucks, feel free to improve it.
                var methods = MethodFinder.GetAllInstanceMethods(type, BindingFlags.Public | BindingFlags.NonPublic);
                foreach (var method in methods)
                    if (MethodSignatureComparer.Instance.Equals(method.GetBaseDefinition(), proxiedMethod))
                    {
                        methodOnTarget = method;
                        break;
                    }
            }
            if (methodOnTarget == null)
                throw new ArgumentException(
                    string.Format(
                        "Could not find method overriding {0} on type {1}. This is most likely a bug. Please report it.",
                        proxiedMethod, type));

            if (genericArguments == null)
                return methodOnTarget;
            return methodOnTarget.MakeGenericMethod(genericArguments);
        }

        private static void PutToCache(MethodInfo methodInfo, Type type, MethodInfo value)
        {
            var key = new KeyValuePair<MethodInfo, Type>(methodInfo, type);
            cache.Add(key, value);
        }
    }
}